/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: Peripheral Component Interconnect bus
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2022-08-13     JasonHu           Init
 */

#include <pci.h>
#include <io.h>
#define NX_LOG_NAME "PCI"
#define NX_LOG_LEVEL NX_LOG_INFO
#include <base/log.h>
#include <base/malloc.h>
#include <base/initcall.h>

NX_PRIVATE NX_List pciDeviceListHead;

NX_PRIVATE void PciDeviceBarInit(NX_PciDeviceBar *bar, NX_U32 addr_reg_val, NX_U32 len_reg_val)
{
    if (addr_reg_val == 0xffffffff)
    {
        addr_reg_val = 0;
    }
    /*we judge type by addr register bit 0, if 1, type is io, if 0, type is memory*/
    if (addr_reg_val & 1)
    {
        bar->type       = PCI_BAR_TYPE_IO;
        bar->base_addr  = addr_reg_val  & PCI_BASE_ADDR_IO_MASK;
        bar->length     = ~(len_reg_val & PCI_BASE_ADDR_IO_MASK) + 1;
    }
    else
    {
        bar->type       = PCI_BAR_TYPE_MEM;
        bar->base_addr  = addr_reg_val  & PCI_BASE_ADDR_MEM_MASK;
        bar->length     = ~(len_reg_val & PCI_BASE_ADDR_MEM_MASK) + 1;
    }
}

void NX_PciDeviceBarDump(NX_PciDeviceBar *bar)
{
    NX_LOG_I("        type: %s, ", bar->type == PCI_BAR_TYPE_IO ? "io base address" : "mem base address");
    NX_LOG_I("        base address: %x, ", bar->base_addr);
    NX_LOG_I("        len: %x", bar->length);
}

NX_PRIVATE void PciDeviceInit(NX_PciDevice *device, NX_U8 bus, NX_U8 dev, NX_U8 function,
                            NX_U16 vendor_id, NX_U16 device_id, NX_U32 class_code,
                            NX_U8 revision_id, NX_U8 multi_function)
{
    device->bus = bus;
    device->dev = dev;
    device->function = function;

    device->vendor_id = vendor_id;
    device->device_id = device_id;
    device->multi_function = multi_function;
    device->class_code = class_code;
    device->revision_id = revision_id;
    int i;
    for (i = 0; i < PCI_MAX_BAR_NR; i++)
    {
        device->bars[i].type = PCI_BAR_TYPE_INVALID;
    }
    device->irq_line = -1;
}

NX_U32 NX_PciReadConfig(NX_U32 bus, NX_U32 device, NX_U32 function, NX_U32 addr)
{
    NX_U32 reg = 0x80000000;
    reg |= (bus & 0xFF) << 16;
    reg |= (device & 0x1F) << 11;
    reg |= (function & 0x7) << 8;
    reg |= (addr & 0xFF) & 0xFC;    /*bit 0 and 1 always 0*/
    IO_Out32(PCI_CONFIG_ADDR, reg);
    return IO_In32(PCI_CONFIG_DATA);
}

void NX_PciWriteConfig(NX_U32 bus, NX_U32 device, NX_U32 function, NX_U32 addr, NX_U32 val)
{
    NX_U32 reg = 0x80000000;
    reg |= (bus & 0xFF) << 16;
    reg |= (device & 0x1F) << 11;
    reg |= (function & 0x7) << 8;
    reg |= (addr & 0xFF) & 0xFC;    /*bit 0 and 1 always 0*/
    IO_Out32(PCI_CONFIG_ADDR, reg);
    IO_Out32(PCI_CONFIG_DATA, val);
}

NX_PRIVATE NX_PciDevice *PciCreateDevice()
{
    NX_PciDevice *device = NX_MemAlloc(sizeof(NX_PciDevice));
    if (device == NX_NULL)
    {
        return NX_NULL;
    }
    NX_ListAddTail(&device->list, &pciDeviceListHead);
    return device;
}

void NX_PciDeviceDump(NX_PciDevice *device)
{
    NX_LOG_I("vendor id:      0x%x", device->vendor_id);
    NX_LOG_I("device id:      0x%x", device->device_id);
    NX_LOG_I("class code:     0x%x", device->class_code);
    NX_LOG_I("revision id:    0x%x", device->revision_id);
    NX_LOG_I("multi function: %d", device->multi_function);
    NX_LOG_I("card bus CIS pointer: %x", device->card_bus_pointer);
    NX_LOG_I("subsystem vendor id: %x", device->subsystem_vendor_id);
    NX_LOG_I("subsystem device id: %x", device->subsystem_device_id);
    NX_LOG_I("expansion ROM base address: %x", device->expansion_rom_base_addr);
    NX_LOG_I("capability list pointer:  %x", device->capability_list);
    NX_LOG_I("irq line: %d", device->irq_line);
    NX_LOG_I("irq pin:  %d", device->irq_pin);
    NX_LOG_I("min Gnt: %d", device->min_gnt);
    NX_LOG_I("max Lat:  %d", device->max_lat);
    int i;
    for (i = 0; i < PCI_MAX_BAR_NR; i++)
    {
        if (device->bars[i].type != PCI_BAR_TYPE_INVALID)
        {
            NX_LOG_I("bar %d:", i);
            NX_PciDeviceBarDump(&device->bars[i]);
        }
    }
    NX_LOG_I(" ");
}

NX_PRIVATE void pci_scan_device(NX_U8 bus, NX_U8 device, NX_U8 function)
{
    NX_U32 val = NX_PciReadConfig(bus, device, function, PCI_DEVICE_VENDER);
    NX_U32 vendor_id = val & 0xffff;
    NX_U32 device_id = val >> 16;
    /*if vendor id is 0xffff, it means that this bus , device not exist!*/
    if (vendor_id == 0xffff || vendor_id == 0)
    {
        return;
    }

    NX_PciDevice *pci_dev = PciCreateDevice();
    if (pci_dev == NX_NULL)
    {
        return;
    }

    val = NX_PciReadConfig(bus, device, function, PCI_BIST_HEADER_TYPE);
    NX_U8 header_type = ((val >> 16));
    val = NX_PciReadConfig(bus, device, function, PCI_STATUS_COMMAND);

    pci_dev->command = val & 0xffff;
    pci_dev->status = (val >> 16) & 0xffff;

    val = NX_PciReadConfig(bus, device, function, PCI_CLASS_CODE_REVISION_ID);
    NX_U32 classcode = val >> 8;
    NX_U8 revision_id = val & 0xff;

    PciDeviceInit(pci_dev, bus, device, function, vendor_id, device_id, classcode, revision_id, (header_type & 0x80));

    int bar, reg;
    for (bar = 0; bar < PCI_MAX_BAR_NR; bar++)
    {
        reg = PCI_BASS_ADDRESS0 + (bar*4);
        val = NX_PciReadConfig(bus, device, function, reg);
        /* set 0xffffffff to bass address[0~5], so that if we NX_PciReadConfig again, it's addr len */
        NX_PciWriteConfig(bus, device, function, reg, 0xffffffff);

       /* PciReadConfig bass address[0~5] to get addr len */
        NX_U32 len = NX_PciReadConfig(bus, device, function, reg);
        /*PciWriteConfig the io/mem address back to confige space */
        NX_PciWriteConfig(bus, device, function, reg, val);

        if (len != 0 && len != 0xffffffff)
        {
            PciDeviceBarInit(&pci_dev->bars[bar], val, len);
        }
    }

    val = NX_PciReadConfig(bus, device, function, PCI_CARD_BUS_POINTER);
    pci_dev->card_bus_pointer = val;

    val = NX_PciReadConfig(bus, device, function, PCI_SUBSYSTEM_ID);
    pci_dev->subsystem_vendor_id = val & 0xffff;
    pci_dev->subsystem_device_id = (val >> 16) & 0xffff;

    val = NX_PciReadConfig(bus, device, function, PCI_EXPANSION_ROM_BASE_ADDR);
    pci_dev->expansion_rom_base_addr = val;

    val = NX_PciReadConfig(bus, device, function, PCI_CAPABILITY_LIST);
    pci_dev->capability_list = val;

    val = NX_PciReadConfig(bus, device, function, PCI_IRQ_PIN_IRQ_LINE);

    int irq = val & 0xff;
    pci_dev->irq_line = irq;
    pci_dev->irq_pin = (val >> 8)& 0xff;

    pci_dev->min_gnt = (val >> 16) & 0xff;
    pci_dev->max_lat = (val >> 24) & 0xff;
}

NX_PRIVATE void NX_PciScanAllBuses()
{
    NX_U32 bus;
    NX_U8 device, function;
    for (bus = 0; bus < PCI_MAX_BUS_NR; bus++)
    {
        for (device = 0; device < PCI_MAX_DEV_NR; device++)
        {
           for (function = 0; function < PCI_MAX_FUN_NR; function++)
            {
                pci_scan_device(bus, device, function);
            }
        }
    }
}

NX_List *NX_PciDeviceListGet(void)
{
    return &pciDeviceListHead;
}

NX_PciDevice *NX_PciDeviceGet(NX_U32 vendor_id, NX_U32 device_id)
{
    NX_PciDevice* device;

    NX_ListForEachEntry(device, &pciDeviceListHead, list)
    {
        if (device->vendor_id == vendor_id && device->device_id == device_id)
        {
            return device;
        }
    }
    return NX_NULL;
}

NX_PciDevice *NX_PciDeviceGetByClassCode(NX_U32 class, NX_U32 sub_class)
{
    NX_PciDevice* device;
    NX_U32 class_code = ((class & 0xff) << 16) | ((sub_class & 0xff) << 8);

    NX_ListForEachEntry(device, &pciDeviceListHead, list)
    {
        if ((device->class_code & 0xffff00) == class_code)
        {
            return device;
        }
    }
    return NX_NULL;
}

void NX_PciEnableBusMastering(NX_PciDevice *device)
{
    NX_U32 val = NX_PciReadConfig(device->bus, device->dev, device->function, PCI_STATUS_COMMAND);
    NX_LOG_D("PCI enable bus mastering: before command: %x", val);
    val |= 0x04;
    NX_PciWriteConfig(device->bus, device->dev, device->function, PCI_STATUS_COMMAND, val);
    val = NX_PciReadConfig(device->bus, device->dev, device->function, PCI_STATUS_COMMAND);
    NX_LOG_D("PCI enable bus mastering: after command: %x", val);
}

NX_U32 NX_PciDeviceRead(NX_PciDevice *device, NX_U32 reg)
{
    return NX_PciReadConfig(device->bus, device->dev, device->function, reg);
}

void NX_PciDeviceWrite(NX_PciDevice *device, NX_U32 reg, NX_U32 value)
{
    NX_PciWriteConfig(device->bus, device->dev, device->function, reg, value);
}

NX_U32 NX_PciDeviceGetIoAddr(NX_PciDevice *device)
{
    int i;
    for (i = 0; i < PCI_MAX_BAR_NR; i++)
    {
        if (device->bars[i].type == PCI_BAR_TYPE_IO)
        {
            return device->bars[i].base_addr;
        }
    }
    return 0;
}

NX_U32 NX_PciDeviceGetMemAddr(NX_PciDevice *device)
{
    int i;
    for (i = 0; i < PCI_MAX_BAR_NR; i++)
    {
        if (device->bars[i].type == PCI_BAR_TYPE_MEM)
        {
            return device->bars[i].base_addr;
        }
    }
    return 0;
}

NX_U32 NX_PciDeviceGetMemLength(NX_PciDevice *device)
{
    int i;
    for(i = 0; i < PCI_MAX_BAR_NR; i++)
    {
        if(device->bars[i].type == PCI_BAR_TYPE_MEM)
        {
            return device->bars[i].length;
        }
    }
    return 0;
}

NX_U32 NX_PciDeviceGetIrqLine(NX_PciDevice *device)
{
    return device->irq_line;
}

NX_PRIVATE NX_U32 PicGetConnectedCounts(void)
{
    NX_U32 n = 0;
    NX_PciDevice *device;
    NX_ListForEachEntry(device, &pciDeviceListHead, list)
    {
        n++;
    }
    return n;
}

NX_PRIVATE void PciDeviceListOne(NX_PciDevice *device)
{
    NX_LOG_I("device bus: %d, device: %d function: %d", device->bus, device->dev, device->function);
    NX_LOG_I("    vendor id:      0x%x", device->vendor_id);
    NX_LOG_I("    device id:      0x%x", device->device_id);
    NX_LOG_I("    class code:     0x%x", device->class_code);
    NX_LOG_I("    irq line: %d", device->irq_line);
    int i;
    for (i = 0; i < PCI_MAX_BAR_NR; i++)
    {
        if (device->bars[i].type != PCI_BAR_TYPE_INVALID)
        {
            NX_LOG_I("    bar %d:", i);
            NX_PciDeviceBarDump(&device->bars[i]);
        }
    }
    NX_LOG_I(" ");
}

void NX_PciDeviceList(void)
{
    NX_LOG_I("list pci device:");
    NX_PciDevice *device;
    NX_ListForEachEntry(device, &pciDeviceListHead, list)
    {
        PciDeviceListOne(device);
        // NX_PciDeviceDump(device);
    }
}

NX_PRIVATE void NX_PCI_Init(void)
{
    NX_ListInit(&pciDeviceListHead);
    NX_PciScanAllBuses();
    NX_LOG_I("PCI device: device found %d.", PicGetConnectedCounts());
}

NX_MODS_INIT(NX_PCI_Init);
